package com.summer.data.dialect.mysql;

import com.summer.data.Jsql;
import com.summer.data.annotation.Comment;
import com.summer.data.annotation.Engine;
import com.summer.data.annotation.column.*;
import com.summer.data.dialect.AbstractDialect;
import com.summer.data.logging.Logger;
import com.summer.data.logging.LoggerFactory;
import com.summer.data.parameter.BlobParameter;
import com.summer.data.parameter.ClobParameter;
import com.summer.data.rs.extractor.KeysExtractor;
import com.summer.data.table.BaseColumn;
import com.summer.data.table.BaseTable;
import com.summer.data.table.NoTableNameException;
import com.summer.data.table.column.*;
import com.summer.data.type.NocaseMetaData;
import com.summer.data.type.Tuple2;
import com.summer.data.util.TableUtils;

import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.sql.JDBCType;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.*;

/**
 * @author 345480567@qq.com
 */
public class MysqlDialect extends AbstractDialect {

    public static final String DEFAULT_VALUE_EMPTY = "empty string";

    private static final Logger logger = LoggerFactory.getLogger(MysqlDialect.class);

    /**
     * MySQL的数据类型
     * 主要包括以下五大类：
     * 整数类型：BIT、BOOL、TINY INT、SMALL INT、MEDIUM INT、 INT、 BIG INT
     * 浮点数类型：FLOAT、DOUBLE、DECIMAL
     * 字符串类型：CHAR、VARCHAR、TINY TEXT、TEXT、MEDIUM TEXT、LONGTEXT、TINY BLOB、BLOB、MEDIUM BLOB、LONG BLOB
     * 日期类型：Date、DateTime、TimeStamp、Time、Year
     * 其他数据类型：BINARY、VARBINARY、ENUM、SET、Geometry、Point、MultiPoint、LineString、MultiLineString、Polygon、GeometryCollection等
     */
    // https://dev.mysql.com/doc/refman/8.0/en/data-types.html

    private static final Map<Class<?>, JDBCType> JAVA_2_JDBC = new HashMap<Class<?>, JDBCType>(){{
        put(Boolean.class, JDBCType.BOOLEAN);
        put(Byte.class, JDBCType.BIT);

        put(Short.class, JDBCType.SMALLINT);
        put(Integer.class, JDBCType.INTEGER);
        put(Long.class, JDBCType.BIGINT);

        put(Float.class, JDBCType.FLOAT);
        put(Double.class, JDBCType.DOUBLE);

        put(BigDecimal.class, JDBCType.DECIMAL);

        put(String.class, JDBCType.VARCHAR);

        // 日期是最复杂的
        put(LocalDate.class, JDBCType.DATE);
        put(LocalTime.class, JDBCType.TIME_WITH_TIMEZONE);
        put(LocalDateTime.class, JDBCType.TIME);

        put(Date.class, JDBCType.DATE);
        put(java.sql.Date.class, JDBCType.DATE);

        put(Timestamp.class, JDBCType.TIMESTAMP);

        put(ClobParameter.class, JDBCType.CLOB);
        put(BlobParameter.class, JDBCType.BLOB);

    }};

    @Override
    public Map<Class<?>, JDBCType> getJDBCTypeMap() {
        return JAVA_2_JDBC;
    }

    public String getPageSql(String string, Integer offset, Integer pageSize) {
        return String.format("%s LIMIT %d, %d ", string, offset, pageSize);
    }

    @Override
    public <T extends BaseTable> List<Jsql> buildCreateTableSql(Class<T> clazz) {

        String tableName = TableUtils.getTableName(clazz);
        if (tableName == null || tableName.isEmpty()) {
            throw new NoTableNameException(clazz);
        }

        List<Jsql> result = new ArrayList<>();

        // 主键列表
        Set<String> primaryKeySet = new HashSet<String>() {
        };
        // 唯一索引
//        Map<String, List<String>> unionIndexMap = new HashMap<>();
        // 索引
        Map<String, List<String>> indexMap = new HashMap<>();

        StringBuffer buffer = new StringBuffer();
        Formatter formatter = new Formatter(buffer);
        formatter.format("CREATE TABLE %s (", tableName);

        Field[] fields = clazz.getFields();
        // 是否有列定义
        boolean hasColumn = false;
        for (Field field : fields) {
            Class<?> fieldType = field.getType();
            if (BaseColumn.class.isAssignableFrom(fieldType)) {
                if (hasColumn) {
                    buffer.append(",");
                }
                hasColumn = true;

                buffer.append(System.lineSeparator());

                String columnName = getColumnName(field);

                if (BoolColumn.class.isAssignableFrom(fieldType)) {
                    formatter.format(" %s %s", columnName, "BOOL");
                } else if (IntegerColumn.class.isAssignableFrom(fieldType)) {
                    AutoGenerated autoGenerated = field.getAnnotation(AutoGenerated.class);
                    if (autoGenerated == null) {
                        formatter.format(" %s INT", columnName);
                    } else {
                        formatter.format(" %s INT NOT NULL AUTO_INCREMENT", columnName);
                        // IntegerColumn 定义了 AutoGenerated，就必须是主键
                        primaryKeySet.add(columnName);
                    }
                } else if (FloatColumn.class.isAssignableFrom(fieldType)) {
                    formatter.format(" %s %s", columnName, "FLOAT");
                } else if (DoubleColumn.class.isAssignableFrom(fieldType)) {
                    formatter.format(" %s %s", columnName, "DOUBLE");
                } else if (DecimalColumn.class.isAssignableFrom(fieldType)) {
                    formatter.format(" %s %s", columnName, "DECIMAL");
                } else if (DateColumn.class.isAssignableFrom(fieldType)) {
                    formatter.format(" %s %s", columnName, "DATE");
                } else if (DateTimeColumn.class.isAssignableFrom(fieldType)) {
                    formatter.format(" %s %s", columnName, "DATETIME");
                } else if (TimeColumn.class.isAssignableFrom(fieldType)) {
                    formatter.format(" %s %s", columnName, "TIME");
                } else if (TimestampColumn.class.isAssignableFrom(fieldType)) {
                    formatter.format(" %s %s", columnName, "TIMESTAMP");
                } else if (StringColumn.class.isAssignableFrom(fieldType)) {
                    MaxLength maxLengthAnnotation = field.getAnnotation(MaxLength.class);
                    int maxLength = 255;
                    if (maxLengthAnnotation != null) {
                        maxLength = maxLengthAnnotation.value();
                    }
                    formatter.format(" %s %s(%d)", columnName, "VARCHAR", maxLength);
                } else if (ClobColumn.class.isAssignableFrom(fieldType)) {
                    formatter.format(" %s %s", columnName, "TEXT");
                } else if (BlobColumn.class.isAssignableFrom(fieldType)) {
                    formatter.format(" %s %s", columnName, "BLOB");
                } else {
                    logger.warn("发现未处理的字段类型：%s", field.getType().getName());
                }

                // 行注释
                Comment commentAnnotation = field.getAnnotation(Comment.class);
                if (commentAnnotation != null) {
                    formatter.format(" COMMENT '%s'", commentAnnotation.value());
                }

                // COLLATE
                if (StringColumn.class.isAssignableFrom(fieldType)) {
                    Collate[] collates = field.getAnnotationsByType(Collate.class);

                    Tuple2<Collate> tuple2 = TableUtils.findBestCollate(collates, this.getClass());
                    if (tuple2.t1 != null) {
                        // 找到了
                        formatter.format(" %s ", tuple2.t1.value());
                    } else if (tuple2.t2 != null) {
                        // 未找到，用默认的
                        formatter.format(" %s ", tuple2.t2.value());
                    }
                }

                // Not Null 处理
                NotNull notNullAnnotation = field.getAnnotation(NotNull.class);
                if (notNullAnnotation != null) {
                    formatter.format(" NOT NULL ");
                }

                // default 处理
                Default defalutAnnotation = field.getAnnotation(Default.class);
                if (defalutAnnotation != null) {
                    formatter.format(" DEFAULT %s ", defalutAnnotation.value());
                }

                // PrimaryKey
                PrimaryKey primaryKeyAnnotation = field.getAnnotation(PrimaryKey.class);
                if (primaryKeyAnnotation != null) {
                    primaryKeySet.add(columnName);
                }

                // 收集唯一索引

                // 收集索引
                Index indexAnnotation = field.getAnnotation(Index.class);
                if (indexAnnotation != null) {
                    String indexName = indexAnnotation.value();
                    if (!indexMap.containsKey(indexName)) {
                        indexMap.put(indexName, new ArrayList<>());
                    }
                    indexMap.get(indexName).add(columnName);
                }

                // 收集HashIndex
                HashIndex hashIndexAnnotation = field.getAnnotation(HashIndex.class);
                if (hashIndexAnnotation != null) {
                    String indexName = hashIndexAnnotation.value();
                    if (indexName.isEmpty()) {
                        indexName = columnName;
                    }
                    result.add(
                            Jsql.format("CREATE INDEX idxh_%s ON %s USING hash (%s);",
                                    indexName, tableName, columnName
                            )
                    );
                }

            }
        }

        // PrimaryKey
        if (!primaryKeySet.isEmpty()) {
            formatter.format(",%sPRIMARY KEY (%s)",
                    System.lineSeparator(),
                    String.join(",", primaryKeySet));
        }

        // 收口1
        formatter.format("%s)", System.lineSeparator());

        // 表的 engine
        Engine engine = clazz.getAnnotation(Engine.class);
        if (engine != null) {
            formatter.format(" ENGINE = %s ", engine.value());
        }
        Engine[] engines = clazz.getAnnotationsByType(Engine.class);
        Tuple2<Engine> engineTuple = TableUtils.findBestEngine(engines, this.getClass());
        if (engineTuple.t1 != null) {
            // 找到了
            formatter.format(" ENGINE = %s ", engineTuple.t1.value());
        } else if (engineTuple.t2 != null) {
            // 未找到，用默认的
            formatter.format(" ENGINE = %s ", engineTuple.t2.value());
        }

        // 表的 charset
        Charset charset = clazz.getAnnotation(Charset.class);
        if (charset != null) {
            formatter.format(" CHARSET = %s ", charset.value());
        }
        Charset[] charsets = clazz.getAnnotationsByType(Charset.class);
        Tuple2<Charset> bestCharset = TableUtils.findBestCharset(charsets, this.getClass());
        if (bestCharset.t1 != null) {
            // 找到了
            formatter.format(" CHARSET = %s ", bestCharset.t1.value());
        } else if (bestCharset.t2 != null) {
            // 未找到，用默认的
            formatter.format(" CHARSET = %s ", bestCharset.t2.value());
        }

        // 表的 Collate
        Collate[] collates = clazz.getAnnotationsByType(Collate.class);
        Tuple2<Collate> bestCollate = TableUtils.findBestCollate(collates, this.getClass());
        if (bestCollate.t1 != null) {
            // 找到了
            formatter.format(" COLLATE = %s ", bestCollate.t1.value());
        } else if (bestCollate.t2 != null) {
            // 未找到，用默认的
            formatter.format(" COLLATE = %s ", bestCollate.t2.value());
        }

        // 表注释
        Comment commentAnnotation = clazz.getAnnotation(Comment.class);
        if (commentAnnotation != null) {
            formatter.format(" COMMENT='%s'", commentAnnotation.value());
        }

        // 收口2
        formatter.format(";");

        // 生成创建索引的 Jsql
        for (Map.Entry<String, List<String>> indexEntry : indexMap.entrySet()) {
            String idxName = indexEntry.getKey();

            if (indexEntry.getValue().isEmpty()) {
                continue;
            }
            result.add(Jsql.format("CREATE INDEX IDX_%s ON %s (%s);",
                    idxName,
                    tableName,
                    String.join(",", indexEntry.getValue())
            ));
        }

        // 加上建表语句
        result.add(0, new Jsql(buffer.toString()));
        return result;

        /**
         create table table_name
         (
         res_id      int auto_increment comment '主键'
         primary key,
         column_name varchar(2) collate utf8mb4_zh_0900_as_cs default null comment 'bbb'
         )
         comment 'xxx' collate = armscii8_bin;


         */
    }

    @Override
    public <T extends BaseTable> List<Jsql> buildModifyTableSql(Class<T> clazz) {

        String tableName = TableUtils.getTableName(clazz);
        if (tableName == null) {
            throw new NoTableNameException(clazz);
        }

        StringBuffer buffer = new StringBuffer();
        Formatter formatter = new Formatter(buffer);
        formatter.format("CREATE TABLE %s (", tableName);

        Field[] fields = clazz.getFields();
        for (int i=0; i<fields.length; i++) {
            Field field = fields[i];
            Class<?> fieldType = field.getType();
            if (BaseColumn.class.isAssignableFrom(fieldType)) {
                if (i != 0){
                    buffer.append(",");
                }
                buffer.append(System.lineSeparator());

                String columnName = getColumnName(field);

                if (IntegerColumn.class.isAssignableFrom(fieldType)) {
                    formatter.format(" %s %s", columnName, "INT");
                } else if (FloatColumn.class.isAssignableFrom(fieldType)) {
                    formatter.format(" %s %s", columnName, "FLOAT");
                } else if (DoubleColumn.class.isAssignableFrom(fieldType)) {
                    formatter.format(" %s %s", columnName, "DOUBLE");
                } else if (DecimalColumn.class.isAssignableFrom(fieldType)) {
                    formatter.format(" %s %s", columnName, "DECIMAL");
                } else if (DateColumn.class.isAssignableFrom(fieldType)) {
                    formatter.format(" %s %s", columnName, "DATE");
                } else if (DateTimeColumn.class.isAssignableFrom(fieldType)) {
                    formatter.format(" %s %s", columnName, "DATETIME");
                } else if (TimeColumn.class.isAssignableFrom(fieldType)) {
                    formatter.format(" %s %s", columnName, "TIME");
                } else if (TimestampColumn.class.isAssignableFrom(fieldType)) {
                    formatter.format(" %s %s", columnName, "TIMESTAMP");
                } else if (StringColumn.class.isAssignableFrom(fieldType)) {
                    MaxLength maxLengthAnnotation = field.getAnnotation(MaxLength.class);
                    int maxLength = 255;
                    if (maxLengthAnnotation != null) {
                        maxLength = maxLengthAnnotation.value();
                    }
                    formatter.format(" %s %s(%d)", columnName, "VARCHAR", maxLength);
                } else if (ClobColumn.class.isAssignableFrom(fieldType)) {
                    formatter.format(" %s %s", columnName, "TEXT");
                } else if (BlobColumn.class.isAssignableFrom(fieldType)) {
                    formatter.format(" %s %s", columnName, "BLOB");
                } else {
                    logger.warn("发现未处理的字段类型：%s", field.getType().getName());
                }
            }
        }
        formatter.format("%s);", System.lineSeparator());

        return new ArrayList<Jsql>() {{
            add(new Jsql(buffer.toString()));
        }};

    }

    @Override
    public KeysExtractor buildKeysExtractor(BaseTable table) {
        try {
            Class<? extends BaseTable> clazz = table.getClass();
            Field[] fields = clazz.getFields();
            for (Field field : fields) {

                if (IntegerColumn.class.isAssignableFrom(field.getType())) {
                    Object fieldObj = field.get(table);
                    IntegerColumn col = (IntegerColumn) fieldObj;

                    AutoGenerated generatedInDBAndReturn = field.getAnnotation(AutoGenerated.class);
                    if (generatedInDBAndReturn != null) {
                        return new MysqlKeysExtractor(col);
                    }

                }
            }
            return null;
        } catch (IllegalArgumentException | IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public Boolean isResultUseColumnName() {
        return false;
    }
}

